#!/usr/bin/perl -w
###############################################################################
#   Meteor
#   An HTTP server for the 2.0 web
#   Copyright (c) 2006-2010 contributing authors
#
#   The Meteor daemon
#
#	Main program should call Meteor::Config::setCommandLineParameters(@ARGV),.
#	Afterwards anybody can access $::CONF{<parameterName>}, where
#	<parameterName> is any valid parameter (except 'Help') listed in the
#	@DEFAULTS array below.
#
###############################################################################
#
#   This program is free software; you can redistribute it and/or modify it
#   under the terms of the GNU General Public License as published by the Free
#   Software Foundation; either version 2 of the License, or (at your option)
#   any later version.
#
#   This program is distributed in the hope that it will be useful, but WITHOUT
#   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#   FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
#   more details.
#
#   You should have received a copy of the GNU General Public License along
#   with this program; if not, write to the Free Software Foundation, Inc.,
#   59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
#   For more information visit www.meteorserver.org
#
###############################################################################

###############################################################################
# meteord version
################################################################################
	
	$::VERSION='1.06.07';
	$::RELEASE_DATE='not yet released';

###############################################################################
# Configuration
###############################################################################
	
	use strict;
	
	use Socket;
	
	use Meteor::Syslog;
	
	use Meteor::Socket;
	use Meteor::Connection;
	use Meteor::Controller;
	use Meteor::Subscriber;
	use Meteor::Channel;
	use Meteor::Document;
	use Meteor::Config;
	
	$::CRLF="\r\n";         # Line separator to be used throughout all modules
	
	our $CONTROL_QUEUE_SIZE=5;
	our $SUBSCRIBER_QUEUE_SIZE=20;
	
	our $MAIN_LOOP_TIMEOUT=60;
	our $AGE_CHECK_INTERVALL=60;
	
	our $MAX_EXIT_DELAY=120;
	
	our $UDP_MAX_MESSAGE_SIZE=8192;

###############################################################################
# Main
###############################################################################
	
	#
	# Record startup time
	# 
	$::STARTUP_TIME=time;
	$::STARTUP_TIME+=0; # avoid warning
	
	#
	# Program name
	#
	$::PGM=$0;
	$::PGM=~s/^.*\///;
	
	#
	# Handle command line options and config file
	#
	Meteor::Config->setCommandLineParameters(@ARGV);
	
	#
	# Do something about warn and die
	#
	unless($::CONF{'Debug'})
	{
		$SIG{'__WARN__'}=\&Meteor::Syslog::myWarn;
		$SIG{'__DIE__'}=\&Meteor::Syslog::myDie;
	}
	
	&::syslog('info',"$::PGM launched!");
	
	#
	# Daemonize
	#
	{
		$0="$::PGM daemon";
		
		my $facility=$::CONF{'SyslogFacility'} || $Meteor::Syslog::DEFAULT_FACILITY;
		
		unless($::CONF{'Debug'} || $facility eq 'none')
		{
			# close standard file descriptors
			close(STDIN);
			close(STDOUT);
			close(STDERR);
			chdir("/");
			umask(0);

			# fork and exit parent
			print STDERR "Exit: Fork\n";
			exit if fork;
			setpgrp(0, $$) if defined $SIG{TTOU};
			$SIG{TTOU}='ignore' if defined $SIG{TTOU};
			
			# Avoid 'stdin reopened for output' warning with newer perls
			open(NULL,'/dev/null');
			<NULL> if(0);
			open(OUT,">/var/run/$::PGM.pid");
			print OUT "$$\n";
			close(OUT);

		}
		else
		{
			&::syslog('info',"PID\t%s",$$);
			open(OUT,">/var/run/$::PGM.pid");
			print OUT "$$\n";
			close(OUT);
		}
	}
	
	#
	# Signal handlers
	#
	$::HUP=$::TERM=$::USR1=$::USR2=0;
	$SIG{'HUP'}=sub{$::HUP=1};
	$SIG{'TERM'}=sub{$::TERM=1};
	$SIG{'USR1'}=sub{$::USR1=1};
	$SIG{'USR2'}=sub{$::USR2=1};
	$SIG{'PIPE'}=sub{&::syslog('info',"Signal PIPE received and ignored\n");};

	#
	# Run server
	#
	my $con_counter=0;
	my $con;
	
	my $controlServer=Meteor::Socket->newServer(
		$::CONF{'ControllerPort'},
		$CONTROL_QUEUE_SIZE,
		$::CONF{'ControllerIP'}
	);
	my $controlServerFN=$controlServer->fileno();
	
	my $subscriberServer=Meteor::Socket->newServer(
		$::CONF{'SubscriberPort'},
		$SUBSCRIBER_QUEUE_SIZE,
		$::CONF{'SubscriberIP'}
	);
	my $subscriberServerFN=$subscriberServer->fileno();
	
	my $udpServer=undef;
	my $udpPort=$::CONF{'UDPPort'};
	my $udpServerFN=undef;
	if($udpPort && $udpPort>0)
	{
		$udpServer=Meteor::Socket->newUDPServer(
			$udpPort,
			$::CONF{'UDPIP'}
		);
		$udpServerFN=$udpServer->fileno();
	}
	
	my $serverVector='';
	vec($serverVector,$controlServerFN,1)=1;
	vec($serverVector,$subscriberServerFN,1)=1;
	vec($serverVector,$udpServerFN,1)=1 if(defined($udpServerFN));
	
	my $lastAgeCheck=time;
	
	my $nextPing=undef;
	if(exists($::CONF{'PingInterval'}) && $::CONF{'PingInterval'}>2)
	{
		$nextPing=$::CONF{'PingInterval'}+$lastAgeCheck;
	}
	
	while(!$::TERM)
	{
		eval
		{
			while(!$::TERM)
			{


				my $rVec=$serverVector;
				my $wVec='';
				my $eVec='';
			
				my $rout;
				my $wout;
				my $eout;
			
				Meteor::Connection->addAllHandleBits(\$rVec,\$wVec,\$eVec);
				
				my $timeout=$MAIN_LOOP_TIMEOUT;
				if(defined($nextPing))
				{
					$timeout=$nextPing-time;
				}
				
				my $result=0;
				if($timeout>0)
				{
					$result=&Meteor::Socket::sselect($rout=$rVec,$wout=$wVec,$eout=$eVec,$timeout);
				}
				
				if($result>0)
				{
					if(vec($rout,$controlServerFN,1))
					{
						Meteor::Controller->newFromServer($controlServer);
					}
					if(vec($rout,$subscriberServerFN,1))
					{
						Meteor::Subscriber->newFromServer($subscriberServer);
					}
					if(defined($udpServerFN) && vec($rout,$udpServerFN,1))
					{
						&handleUDP($udpServer);
					}
					
					Meteor::Connection->checkAllHandleBits($rout,$wout,$eout);
				}
				elsif($result<0)
				{
					&::syslog('crit',"Select failed: $!");
					sleep(30);
				}
				
				if($::HUP)
				{
					$::HUP=0;
					
					&::syslog('info',"Received SIGHUP, re-reading config, reopening logs and clearing document cache!");
					
					Meteor::Syslog->cycleLogFileHandle();
					Meteor::Config->readConfig();
					Meteor::Config->updateConfig();
					
					Meteor::Document->clearDocuments();
				}
				
				if($::USR1)
				{
					$::USR1=0;
					
					&::syslog('info',"Received SIGUSR1, clearing channel buffers!");
					
					Meteor::Channel->clearAllBuffers();
				}

				if($::USR2)
				{
					$::USR2=0;
					
					&::syslog('info',"Received SIGUSR2, clearing document cache!");
					
					Meteor::Document->clearDocuments()
				}

				my $t=time;
				if($t>$lastAgeCheck+$AGE_CHECK_INTERVALL)
				{
					my $minTimeStap=time-$::CONF{'MaxMessageAge'};
					Meteor::Channel->trimMessageStoresByTimestamp($minTimeStap);
					$lastAgeCheck=time;
					$t=$lastAgeCheck;
					
					Meteor::Subscriber->checkPersistentConnectionsForMaxTime();
					Meteor::Connection->destroyBadRequests();
				}
				
				if(defined($nextPing) && $nextPing<=$t)
				{
					$nextPing=undef;
					
					Meteor::Subscriber->pingPersistentConnections();
					
					if(exists($::CONF{'MaxMessageAge'}) && $::CONF{'MaxMessageAge'}>2)
					{
						$nextPing=$::CONF{'PingInterval'}+time;
					}
				}
			}
		};
		unless($::TERM)
		{
			&::syslog('alert',"$::PGM loop died (will restart in 2 seconds): $@");
			sleep(2);
		}
	}
	
	#
	# Proper shutdown
	#
	if($::TERM)
	{
		&::syslog('info',"Received SIGTERM, begin shutdown!");
		
		$subscriberServer->close();
		$controlServer->close();
		
		unlink("/var/run/$::PGM.pid") unless($::CONF{'Debug'});
		
		Meteor::Connection->closeAllConnections();
		
		my $timoutAt=time+$MAX_EXIT_DELAY;
		
		while(Meteor::Connection->connectionCount() && time<$timoutAt)
		{
			my $rVec='';
			my $wVec='';
			my $eVec='';
			
			my $rout;
			my $wout;
			my $eout;
			
			Meteor::Connection->addAllHandleBits(\$rVec,\$wVec,\$eVec);
			
			my $result=&Meteor::Socket::sselect($rout=$rVec,$wout=$wVec,$eout=$eVec,$timoutAt-time);
			
			if($result>0)
			{
				Meteor::Connection->checkAllHandleBits($rout,$wout,$eout);
			}
		}
		
		if(my $cnt=Meteor::Connection->connectionCount())
		{
			&::syslog('info',"$cnt client(s) unresponsive, will shutdown anyway");
			
			print STDERR "Exit: TERM Shutdown (unresponsive clients)\n";
			exit(1);
		}
		
		&::syslog('info',"shutdown succeeded");
		
		print STDERR "Exit: TERM Shutdown (clean)\n";
		exit(0);
	}
	
	&::syslog('emerg',"$::PGM loop exited");

###############################################################################
# Subroutines
###############################################################################
sub handleUDP {
	$udpServer=shift;
	
	my $line;
	my $hispaddr=recv($udpServer->{'handle'},$line,$::UDP_MAX_MESSAGE_SIZE,0);
	
	&::syslog('debug',"udp message received: %s",$line);
	
	return unless($line=~s/^(\S+)\s//);
	
	my $cmd=$1;
	
	if($cmd eq 'ADDMESSAGE')
	{
		return unless($line=~s/^(\S+)\s//);
		
		my $channelName=$1;
		my $channel=Meteor::Channel->channelWithName($channelName);
		my $msg=$channel->addMessage($line);
		my $msgID=$msg->id();
		&::syslog('debug',"udp: new message added, ID %s",$msgID);
	}
}

1;
############################################################################EOF
